﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Text;

namespace Neitzel.Irc
{
    /// <summary>
    /// IRC Message that could be send / received through a IRCSocket
    /// </summary>
    public class IrcMessage : INotifyPropertyChanged
    {        
        #region Fields

        /// <summary>
        /// Tracesource for all log messages.
        /// </summary>
        private static readonly TraceSource Logger = new TraceSource(IrcConstants.TraceSourceName);

        #endregion 

        #region Constants

        /// <summary>
        /// List of Characters that are only allowed inside the last argument.
        /// If such a character is inside the last argument, the argument must be prefixed by a ":"
        /// </summary>
        private readonly char[] _charsAllowedOnlyInLastArgument = { ' ', ':', '\t' };

        /// <summary>
        /// List of WhiteSpace Characters
        /// - Space
        /// - Tab
        /// </summary>
        private readonly char[] _whiteSpaceCharacters = { ' ', '\t' };

        /// <summary>
        /// NICK 0: Nickname
        /// </summary>
        public const string NickMessage = "NICK {0}";

        /// <summary>
        /// PONG 0: Reply
        /// </summary>
        public const string PongMessage = "PONG {0}";

        /// <summary>
        /// PRIVMSG 0: Target, 1: Message
        /// </summary>
        public const string PrivmsgMessage = "PRIVMSG  {0} :{1}";

        /// <summary>
        /// USER 0: Username, 1: Hostname, 2: ServerName, 3: Full Name 
        /// </summary>
        public const string UserMessage = "USER {0} {1} {2} :{3}";

        #endregion

        #region Lifetime

        /// <summary>
        /// Initializes a new instance of the IrcMessage class.
        /// </summary>
        /// <param name="prefix">Prefix of the message</param>
        /// <param name="command">IRC command.</param>
        /// <param name="parameter">Arguments divided by space</param>
        public IrcMessage(string prefix, string command, string parameter)
        {
            // validate
            if (command == null) throw new ArgumentNullException(nameof(command));
            
            _prefix = prefix;
            _command = command.ToUpper(CultureInfo.InvariantCulture);
            _parameter = new ObservableCollection<string>();
            if (parameter != null)
                ParseParameter(parameter);
        }

        /// <summary>
        /// Initializes a new instance of the IrcMessage class.
        /// </summary>
        /// <param name="message">Message string as it was sent.</param>
        public IrcMessage(string message)
        {
            // validate
            if (message == null) throw new ArgumentNullException(nameof(message));
            
            _parameter = new ObservableCollection<string>();
            _parameter.CollectionChanged +=
                (sender, e) => OnPropertyChanged(this, new PropertyChangedEventArgs("Parameter"));

            var stringToEvaluate = message.Trim();
            int placeOfSpace;
            if (stringToEvaluate.StartsWith(":", StringComparison.OrdinalIgnoreCase))
            {
                // Message contains a Prefix!
                placeOfSpace = stringToEvaluate.IndexOf(" ", StringComparison.OrdinalIgnoreCase);
                _prefix = stringToEvaluate.Substring(1, placeOfSpace);
                stringToEvaluate = stringToEvaluate.Substring(placeOfSpace).Trim();
            }

            placeOfSpace = stringToEvaluate.IndexOf(" ", StringComparison.OrdinalIgnoreCase);
            if (placeOfSpace > 0)
            {
                Command = stringToEvaluate.Substring(0, placeOfSpace).ToUpper(CultureInfo.InvariantCulture);
                ParseParameter(stringToEvaluate.Substring(placeOfSpace).Trim());
            }
            else
            {
                Command = stringToEvaluate.ToUpper(CultureInfo.InvariantCulture);
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the prefix of the message.
        /// </summary>
        public string Prefix
        {
            get { return _prefix; }
            set { _prefix = value; OnPropertyChanged(this, new PropertyChangedEventArgs("Prefix")); }
        }
        private string _prefix;

        /// <summary>
        /// Gets or sets the command of the message (Should always be stored in uppercase!)
        /// </summary>
        public string Command
        {
            get { return _command; }
            set { _command = value; OnPropertyChanged(this, new PropertyChangedEventArgs("Command")); }
        }
        private string _command;

        /// <summary>
        /// Gets all parameters of the IRC command.
        /// </summary>
        public ObservableCollection<string> Parameter
        {
            get { return _parameter; } 
            private set 
            { 
                _parameter = value;
                OnPropertyChanged(this, new PropertyChangedEventArgs(nameof(Parameter)));
                _parameter.CollectionChanged += (sender, e) => OnPropertyChanged(this, new PropertyChangedEventArgs("Parameter"));
            }
        }
        private ObservableCollection<string> _parameter;
 
        /// <summary>
        /// Gets all parameter as string as it is required to send them to the IRC ServerConfig
        /// </summary>
        /// <returns>The Parameter as string.</returns>
        public string Parameters
        {
            get
            {
                var parameter = new StringBuilder();
                for (int i = 0; i < Parameter.Count; i++)
                {
                    parameter.Append(" ");

                    // Add a ":" in front of last Argument if Argument contains a special Character that is only allowed in last Argument
                    if ((i + 1 == Parameter.Count) && Parameter[i].Contains(_charsAllowedOnlyInLastArgument))
                        parameter.Append(":");

                    parameter.Append(Parameter[i]);
                }

                return parameter.ToString().Trim();
            }
        }

        /// <summary>
        /// Gets all parameter as string to display it (without the : in front of last parameter)
        /// </summary>
        /// <returns>The Parameter as string.</returns>
        public string VisualParameters
        {
            get
            {
                var parameter = new StringBuilder();
                foreach (string t in Parameter)
                {
                    parameter.Append(" ");
                    parameter.Append(t);
                }

                return parameter.ToString().Trim();
            }
        }

        #endregion

        #region Public Members

        /// <summary>
        /// Get this IRCMessage as byte[]
        /// </summary>
        /// <returns>a list of bytes with \r\n Combination at the end</returns>
        public byte[] ToByteArray()
        {
            return Encoding.ASCII.GetBytes(ToString() + "\r\n");
        }

        /// <summary>
        /// Get the User from the Prefix
        /// </summary>
        /// <returns>Userpart of Prefix</returns>
        public string UserOfPrefix()
        {
            // Check if we got a prefix first.
            if (String.IsNullOrWhiteSpace(Prefix)) return String.Empty;

            var posExclam = Prefix.IndexOf("!", StringComparison.Ordinal);
            return (posExclam > 0) ? Prefix.Substring(0, posExclam) : String.Empty;
        }

        /// <summary>
        /// Get the Message as String - as it will be sent
        /// </summary>
        /// <returns>the Message as String without a \r\n at the end</returns>
        public override string ToString()
        {
            // Create a StringBuilder to create the required String
            var text = new StringBuilder();

            // Add the Prefix if the Prefix is not Null or empty
            if (!String.IsNullOrEmpty(Prefix))
            {
                text.Append(":");
                text.Append(Prefix);
                text.Append(" ");
            }

            // Append the Command
            text.Append(Command);

            // Append the Parameters
            text.Append(" ");
            text.Append(Parameters);

            // Return the created text without spaces at start or end.
            return text.ToString().Trim();
        }

        #endregion

        #region Private Members

        /// <summary>
        /// Get the individual Parameters from a string with all Parameters
        /// If a parameter starts with a :, it will be the last parameter (till end of string)
        /// </summary>
        /// <param name="param">All Arguments as single String.</param>
        private void ParseParameter(string param)
        {
            // Check if we are at last Argument because of ":" in front.
            if (param.StartsWith(":", StringComparison.OrdinalIgnoreCase))
            {
                // Add argument without the :
                Parameter.Add(param.Substring(1));
                return;
            }

            // Check if String Contains a not allowed Character
            int locationSpace = param.FirstIndexOf(_whiteSpaceCharacters);

            // if no Whitespace is found: Add parameter
            if (locationSpace == 0)
            {
                Parameter.Add(param);
                return;
            }

            // Add String till Whitespace
            Parameter.Add(param.Substring(0, locationSpace));

            // Work on Rest of String
            ParseParameter(param.Substring(locationSpace + 1).Trim());
        }

        #endregion

        #region INotifyPropertyChanged parts

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // prevent racing conditions
            var handler = PropertyChanged;

            // Invoke Event
            handler?.Invoke(sender, e);
        }

        #endregion
    }
}
